/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Written by Mark Hemment, 1996 (markhe@nextd.demon.co.uk).
 *
 * (C) SGI 2006, Christoph Lameter
 * 	Cleaned up and restructured to ease the addition of alternative
 * 	implementations of SLAB allocators.
 * (C) Linux Foundation 2008-2013
 *      Unified interface for all slab allocators
 */

#ifndef SEMINIX_SLAB_H
#define	SEMINIX_SLAB_H

#include <seminix/overflow.h>
#include <seminix/page.h>
#include <seminix/getorder.h>
#include <seminix/gfp.h>
#include <seminix/cache.h>

typedef unsigned int __bitwise slab_flags_t;

#define SLAB_POISON			((slab_flags_t)0x00000800U)	/* DEBUG: Poison objects */
#define SLAB_HWCACHE_ALIGN	((slab_flags_t)0x00002000U)	/* Align objs on cache lines */
#define SLAB_CACHE_DMA		((slab_flags_t)0x00004000U)	/* Use GFP_DMA memory */
#define SLAB_PANIC			((slab_flags_t)0x00040000U)	/* Panic if kmem_cache_create() fails */

struct kmem_cache_cpu {
    void **freelist;
    struct page *page;
    unsigned int offset;
    unsigned int objsize;
};

struct kmem_cache_node {
    spinlock_t list_lock; /* 保护 list 和 nr_partial */
    unsigned long nr_partial;
    struct list_head partial;
    atomic_long_t nr_slabs;
};

struct kmem_cache {
    unsigned long flags;
    int size;       /* 每个 object 的大小(包含对齐的大小) */
    int objsize;    /* 每个 object 的原始大小(objsize <= size)*/
    int offset;     /* Free pointer offset(默认为 0) */
    int order;

    /* 避免使用额外的 cacheline */
    struct kmem_cache_node local_node;

    /* 管理 slab 的分配和释放 */
    int objects;    /* 这个 kmem_cache 中 object 对象的总数 */
    int refcount;   /* kmem_cache 的引用计数, 为 0 将被释放 */
    void (*ctor)(void *);
    int inuse;      /* 每个 object 对齐地址后的大小(inuse = ALIGN(objsize, sizeof void*)) */
    int align;
    const char *name;
    struct list_head list; /* List of slab_caches */

    struct kmem_cache_cpu *cpu_slab[CONFIG_NR_CPUS];
};

#if defined(ARCH_DMA_MINALIGN) && ARCH_DMA_MINALIGN > 8
#define ARCH_KMALLOC_MINALIGN ARCH_DMA_MINALIGN
#define KMALLOC_MIN_SIZE ARCH_DMA_MINALIGN
#else
#define ARCH_KMALLOC_MINALIGN __alignof__(unsigned long long)
#endif

#define KMALLOC_SHIFT_LOW ilog2(ARCH_KMALLOC_MINALIGN)

/*
 * We keep the general caches in an array of slab caches that are used for
 * 2^x bytes of allocations.
 */
extern struct kmem_cache kmalloc_caches[PAGE_SHIFT];

/*
 * Sorry that the following has to be that ugly but some versions of GCC
 * have trouble with constant propagation and loops.
 */
static __always_inline int kmalloc_index(usize size)
{
    if (!size)
        return 0;

    if (size <= KMALLOC_MIN_SIZE)
        return KMALLOC_SHIFT_LOW;

    if (size > 64 && size <= 96)
        return 1;
    if (size > 128 && size <= 192)
        return 2;
    if (size <=          8) return 3;
    if (size <=         16) return 4;
    if (size <=         32) return 5;
    if (size <=         64) return 6;
    if (size <=        128) return 7;
    if (size <=        256) return 8;
    if (size <=        512) return 9;
    if (size <=       1024) return 10;
    if (size <=   2 * 1024) return 11;
/*
 * The following is only needed to support architectures with a larger page
 * size than 4k.
 */
    if (size <=   4 * 1024) return 12;
    if (size <=   8 * 1024) return 13;
    if (size <=  16 * 1024) return 14;
    if (size <=  32 * 1024) return 15;
    if (size <=  64 * 1024) return 16;
    if (size <= 128 * 1024) return 17;
    if (size <= 256 * 1024) return 18;
    if (size <= 512 * 1024) return 19;
    if (size <= 1024 * 1024) return 20;
    if (size <=  2 * 1024 * 1024) return 21;

    return -1;
}

/*
 * Find the slab cache for a given combination of allocation flags and size.
 *
 * This ought to end up with a global pointer to the right cache
 * in kmalloc_caches.
 */
static __always_inline struct kmem_cache *kmalloc_slab(usize size)
{
    int index = kmalloc_index(size);

    if (index == 0)
        return NULL;

    return &kmalloc_caches[index];
}

/*
 * ZERO_SIZE_PTR will be returned for zero sized kmalloc requests.
 *
 * Dereferencing ZERO_SIZE_PTR will lead to a distinct access fault.
 *
 * ZERO_SIZE_PTR can be passed to kfree though in the same way that NULL can.
 * Both make kfree a no-op.
 */
#define ZERO_SIZE_PTR ((void *)16)

#define ZERO_OR_NULL_PTR(x) ((unsigned long)(x) <= \
                (unsigned long)ZERO_SIZE_PTR)

/*
 * Setting ARCH_SLAB_MINALIGN in arch headers allows a different alignment.
 * Intended for arches that get misalignment faults even for 64 bit integer
 * aligned buffers.
 */
#ifndef ARCH_SLAB_MINALIGN
#define ARCH_SLAB_MINALIGN __alignof__(unsigned long long)
#endif

void kmem_cache_init(void);

struct kmem_cache *kmem_cache_create(const char *name, usize size,
        usize align, unsigned long flags, void (*ctor)(void *));
void kmem_cache_destroy(struct kmem_cache *s);

/*
 * Please use this macro to create slab caches. Simply specify the
 * name of the structure and maybe some flags that are listed above.
 *
 * The alignment of the struct determines object alignment. If you
 * f.e. add ____cacheline_aligned_in_smp to the struct declaration
 * then the objects will be properly aligned in SMP configurations.
 */
#define KMEM_CACHE(__struct, __flags)					\
        kmem_cache_create(#__struct, sizeof(struct __struct),	\
            __alignof__(struct __struct), (__flags), NULL)

void *kmem_cache_alloc(struct kmem_cache *, gfp_t);
void kmem_cache_free(struct kmem_cache *, void *);

unsigned int kmem_cache_size(struct kmem_cache *);
const char *kmem_cache_name(struct kmem_cache *);
int kmem_ptr_validate(struct kmem_cache *cachep, const void *ptr);

void kfree(const void *);
usize ksize(const void *);

void *__kmalloc(usize size, gfp_t flags);

static __always_inline void *kmalloc(usize size, gfp_t flags)
{
    if (__builtin_constant_p(size)) {
        if (size > PAGE_SIZE / 2)
            return (void *)__get_free_pages(flags,
                            get_order(size));

        if (!(flags & GFP_DMA)) {
            struct kmem_cache *s = kmalloc_slab(size);

            if (!s)
                return ZERO_SIZE_PTR;

            return kmem_cache_alloc(s, flags);
        }
    }
    return __kmalloc(size, flags);
}

/*
 * Shortcuts
 */
static inline void *kmem_cache_zalloc(struct kmem_cache *k, gfp_t flags)
{
    return kmem_cache_alloc(k, flags | __GFP_ZERO);
}

/**
 * kzalloc - allocate memory. The memory is set to zero.
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate (see kmalloc).
 */
static inline void *kzalloc(usize size, gfp_t flags)
{
    return kmalloc(size, flags | __GFP_ZERO);
}

/**
 * kmalloc_array - allocate memory for an array.
 * @n: number of elements.
 * @size: element size.
 * @flags: the type of memory to allocate (see kmalloc).
 */
static inline void *kmalloc_array(usize n, usize size, gfp_t flags)
{
    usize bytes;

    if (unlikely(check_mul_overflow(n, size, &bytes)))
        return NULL;
    if (__builtin_constant_p(n) && __builtin_constant_p(size))
        return kmalloc(bytes, flags);
    return __kmalloc(bytes, flags);
}

/**
 * kcalloc - allocate memory for an array. The memory is set to zero.
 * @n: number of elements.
 * @size: element size.
 * @flags: the type of memory to allocate (see kmalloc).
 */
static inline void *kcalloc(usize n, usize size, gfp_t flags)
{
    return kmalloc_array(n, size, flags | __GFP_ZERO);
}

#endif /* !SEMINIX_SLAB_H */
